--------------King's Quest-------------
A 4am and san inc crack      2018-01-15
-------------------. updated 2018-01-19
                   |___________________

Name: King's Quest
Genre: adventure
Year: 1984
Credits: designed by Roberta Williams;
  Apple II version by Jeff Stephenson;
  original version by Arthur Abraham,
  Charles Tingley, Ken MacNeill, Doug
  MacNeill, and Greg Rowland
Publisher: Sierra On-Line
Platform: Apple //e or later (128K)
Media: 2 double-sided 5.25-inch disks
  (3 sides total)
OS: DOS 3.3

This disk was automatically cracked by
Passport, using qkumba's universal
Sierra patcher. Here is the transcript
for disk A:

                 --v--

READING FROM S6,D1
T00,S00 FOUND DOS 3.3 BOOTLOADER
USING DISK'S OWN RWTS
WRITING TO S5,D2
T0E,S0F FOUND SIERRA PROTECTION CHECK
T0E,S0F,$06: 20 -> 60
T0C,S01,$35: 1B -> 19
T0B,S0B,$72: D5 -> DD
T0B,S0D,$AA: F9 -> F8
CRACK COMPLETE.

                 --^--

More information and source code is
available at
https://archive.org/details/Passport4am

Other sides are unprotected.

Quod erat liberand one more thing...

                   ~

Every DOS 3.3-formatted Apple II floppy
has a "disk volume number." It is set
when you format the disk, is stored in
the address field of every sector, and
is displayed when you issue the CATALOG
command. The disk volume number is
almost always ignoreable, except when
it isn't.

In this game, it isn't.

All 3 sides, including the boot disk,
and even the data disks you create to
save and restore games, have non-
standard disk volume numbers.

  - disk A: 001
  - disk B: 002
  - disk A: 003
  - data disks (created within the game
    by pressing "4"): 010

And they matter. The game uses the disk
volume number to know whether the right
disk is in the drive at any particular
time.

Why is this a problem? In the physical
world, it isn't. Unprotected disks can
have any disk volume number and still
be copyable with third-party tools. But
it's 2018, and we don't traffic in
physical objects anymore; we traffic in
disk images.

Due to poor historical choices, the
dominant format for disk images, .DSK,
does not store the disk volume number.
This presents us with a modern dilemma
of our own design: either release disk
images in some other format (easy, but
could reduce compatibility or require
more disk space or both), or change the
game code to distinguish disks in some
other way (hard, game-specific, insane,
why would you do that).

Obviously, we chose the hard way.

To distinguish disks that are really
.DSK files, we need to store a unique
marker somewhere on the disk that will
actually be stored in the .DSK file.
Since .DSK files only contain sector
data from track $00 to track $22, that
means we need to store a byte in a
sector. That part is non-negotiable.
Data must be stored somewhere, and if
.DSK files only store sector data, we
need to store the fake volume number as
sector data.

Of course, the game doesn't make this
easy for us. There isn't actually one
sector that is unused across all disks.
(Trust me, we checked.) But that's not
even the worst part.

This game has an in-game command to
format a data disk. The data disk is
formatted with -- you guessed it -- a
nonstandard disk volume number, which
is later checked to ensure the data
disk is in the drive during the "SAVE
GAME" or "RESTORE GAME" command.

Here's what we're going to do. We're
going to store the disk volume number
in a sector that has real data,

AND intercept the game's low-level disk
reading routine so that instead of
getting the disk volume from the
address field, it gets it from that
magic byte in that magic sector,

AND intercept low-level format commands
to write the disk volume number to that
magic byte of that magic sector,

AND intercept low-level read commands
of that magic sector to return the real
sector data that belongs there instead
of the magic byte,

AND cache the magic byte from the magic
sector so we're not actually reading
the magic sector for every single disk
access (which would totally destroy the
performance of an already slow medium).

I said it was insane. You were warned.

                   ~

Our code begins at $BEB0. This region
is supposed to be part of the format
disk routine, but it is now unused for
reasons that will be explained shortly.

[Note: source code for this routine was
 provided along with this write-up. If
 you didn't get it, well, I don't know
 what to tell you. In the long run we
 are all dead.]

[Note the second: qkumba wrote this
 code. I mostly just cheered.]

*BEB0L

; Save address of RWTS parameter table
BEB0-   84 48       STY   $48
BEB2-   85 49       STA   $49

; Check the requested disk volume
BEB4-   A0 03       LDY   #$03
BEB6-   B1 48       LDA   ($48),Y

; 0 = wildcard = always continue
BEB8-   F0 4A       BEQ   $BF04

; Otherwise check if it's $FE
BEBA-   AA          TAX
BEBB-   E8          INX
BEBC-   E0 FF       CPX   #$FF

; No, it's a non-standard disk volume
BEBE-   90 02       BCC   $BEC2

; Yes, it was $FE -- change it to $01
; to fake out the caller (the game will
; refuse to boot on a disk with the
; wrong disk volume, because f--- you)
BEC0-   A9 01       LDA   #$01

; Either way, store the requested disk
; volume because we'll need it later
BEC2-   8D FE BE    STA   $BEFE

; If we're trying to read the boot disk,
; skip ahead (the carry is still set
; from the CMP at $BEBC)
BEC5-   B0 3D       BCS   $BF04

; The other disks need special handling
; because they can be requested at any
; time. First off, fake out the real
; RWTS by putting a 0 in the requested
; disk volume
BEC7-   A9 00       LDA   #$00
BEC9-   91 48       STA   ($48),Y
BECB-   CA          DEX

; X holds the requested disk volume --
; check if it's the same as the last
; RWTS command
BECC-   EC 00 BF    CPX   $BF00

; Yes, they match, which means we can
; go ahead and execute the RWTS command
BECF-   F0 33       BEQ   $BF04

; No, this RWTS command is requesting a
; different disk volume than the last
; RWTS command. It's time for our magic
; act!
BED1-   20 30 BF    JSR   $BF30

Track $22, sector $0F is the best
choice to store our fake disk volume
byte, for one reason: it is unused on
the save game disk. None of the other
disks are writeable, so we won't have
to intercept writes to the sector,
which simplifies the code enormously.

*BF30L

; swap out the requested track/sector/
; command with
;   track = $22
;   sector = $0F
;   address = $BF00
;   command = $01 (read)
; and save the original values so we
; can restore them later
BF30-   A2 08       LDX   #$08
BF32-   BC 47 BF    LDY   $BF47,X
BF35-   B1 48       LDA   ($48),Y
BF37-   48          PHA
BF38-   BD 46 BF    LDA   $BF46,X
BF3B-   91 48       STA   ($48),Y
BF3D-   68          PLA
BF3E-   9D 46 BF    STA   $BF46,X
BF41-   CA          DEX
BF42-   CA          DEX
BF43-   10 ED       BPL   $BF32
BF45-   60          RTS
BF46-   01 0C BF 09 00 08 0F 05 22 04

Continuing from $BED4...

; After swapping the RWTS parameters,
; the accumulator has the original RWTS
; command. Check if it's a format
; command -- i.e. the game is trying to
; initialize a disk for saved games.
BED4-   C9 04       CMP   #$04

; no (whew)
BED6-   D0 08       BNE   $BEE0

; Yes, this is a format command. Ugh.
; We're not really going to format the
; disk. Instead, we're going to assume
; the disk is already formatted, and
; write the disk volume as regular data
; in our special sector (T22,S0F).
; First, convert $04 (format command)
; to $02 (write command).
BED8-   4A          LSR
BED9-   91 48       STA   ($48),Y

; The expected disk volume of a save
; game disk is $0A, so set that as the
; first byte in the buffer
BEDB-   A9 0A       LDA   #$0A
BEDD-   8D 00 BF    STA   $BF00

; alter underlying RWTS code so it only
; reads the first byte of the sector
BEE0-   EE 3D BE    INC   $BE3D

BEE3-   20 2D BF    JSR   $BF2D

*BF2DL

; call the real RWTS
BF2D-   20 04 BD    JSR   $BD04

After the real RWTS returns, $BF2D will
fall through to $BF30, which is the
routine we called earlier to swap the
track/sector/command in the RWTS
parameter table. So now they've swapped
back to their original values, and we
have the first byte of T22,S0F in $BF00
(which is actually right in the middle
of our code, which is totally the kind
of thing that qkumba would do).

Continuing from $BEE6...

; restore underlying RWTS code so it
; reads full sectors again
BEE6-   CE 3D BE    DEC   $BE3D

; Restore read command
BEE9-   A9 01       LDA   #$01
BEEB-   8D 46 BF    STA   $BF46

; If the real RWTS came back with an
; error, cancel all the magic and
; propagate the error back to the
; caller
BEEE-   B0 2E       BCS   $BF1E

[Where's the $BEEF? In crack no. 1611.]

; Set the error in the RWTS parameter
; table to "disk volume mismatch."
; (This will only be checked by the
; caller if we return with the carry
; bit set, which we haven't decided
; yet, so it's safe to do this now.)
BEF0-   B1 48       LDA   ($48),Y
BEF2-   AA          TAX
BEF3-   C8          INY
BEF4-   A9 20       LDA   #$20
BEF6-   91 48       STA   ($48),Y

; If the original RWTS command was $04
; (format), we've done all the magic
; we're going to do today, so tell the
; caller that it worked and be happy.
BEF8-   E0 04       CPX   #$04
BEFA-   18          CLC
BEFB-   F0 21       BEQ   $BF1E

; These next two instructions are self-
; modifying code. $BEFE is the disk
; volume that was originally requested
; (set at $BEC2), and $BF00 is the disk
; volume that we want the caller to
; believe is currently in the drive
; (set by the underlying RWTS after we
; hacked it to read only one byte from
; T22,S0F).
BEFD-   A9 01       LDA   #$01
BEFF-   C9 01       CMP   #$01

; If they don't match, set the carry to
; indicate an error (we already set the
; RWTS error code to "disk volume
; mismatch, at $BEF6), then exit via
; the cleanup routine at $BF1E which
; will complete the illusion by setting
; the requested and found disk volume
; in the RWTS parameter table.
BF01-   38          SEC
BF02-   D0 1A       BNE   $BF1E

There are 4 possible ways we can end up
here.

  1) The caller requested a wildcard
     disk volume ($00), so we branched
     from $BEB8.

  2) The caller requested disk volume
     $FE, so we branched from $BEC5.

  3) The caller requested a disk volume
     of one of the game disks ($01-$03,
     or $0A to read from the save game
     disk), but it was the same disk
     volume as the previous RWTS call.
     We assume the correct disk is
     already in the drive and branched
     here from $BECF.

  4) The caller requested a disk volume
     of one of the game disks, it was
     different from the previous RWTS
     call, we did our magic to get the
     fake volume from T22,S0F and
     discovered that the correct disk
     is now in the drive, so we fell
     through from $BF02.

; Execute the original RWTS command
BF04-   20 04 BD    JSR   $BD04

; Didn't work, propagate the error and
; exit
BF07-   B0 15       BCS   $BF1E

Hooray! The original RWTS command
succeeded! Just one last thing...

                   ~

; Was this the game trying to read
; T22,S0F?
BF09-   A0 04       LDY   #$04
BF0B-   B1 48       LDA   ($48),Y
BF0D-   C9 22       CMP   #$22
BF0F-   D0 0C       BNE   $BF1D
BF11-   C8          INY
BF12-   B1 48       LDA   ($48),Y
BF14-   C9 0F       CMP   #$0F
BF16-   D0 05       BNE   $BF1D

; Yes, which means the first byte of
; the sector data we just read is the
; fake disk volume. Change that to the
; real data before returning to the
; caller. Presto-chango!
BF18-   A9 00       LDA   #$00
BF1A-   A8          TAY
BF1B-   91 3E       STA   ($3E),Y

[In this game, all disks have a #$00
 as the first byte of T22,S0F, so
 swapping out the real sector data is
 easy -- it's always #$00! But if that
 weren't the case, we could keep a map
 of expected data indexed by disk
 volume. That may be required if we use
 this code for other games.]

[This technique is called 4shadowing.]

; Reset the carry to tell the caller
; that the RWTS command succeeded. (The
; CMPs to check the track and sector
; messed it up.)
BF1D-   18          CLC

All code paths lead here. This is the
final cleanup that we always do before
returning to the caller: setting the
requested and found disk volumes in the
RWTS parameter table. This does not
affect the carry bit.

BF1E-   A0 03       LDY   #$03
BF20-   AD FE BE    LDA   $BEFE
BF23-   91 48       STA   ($48),Y
BF25-   A0 0E       LDY   #$0E
BF27-   AD 00 BF    LDA   $BF00
BF2A-   91 48       STA   ($48),Y
BF2C-   60          RTS

Now, each disk gets its own magic byte
on track $22, sector $0F:

disk A: T22,S0F,$00: 00 -> 01
disk B: T22,S0F,$00: 00 -> 02
disk C: T22,S0F,$00: 00 -> 03

Finally, we need one single patch. All
RWTS calls go through $B7B5, which in
turn calls $BD00. If we change that to
call $BEB0 instead, the illusion will
be complete.

T00,S01,$B8: 00BD -> B0BE

Quod erat liberandum.

                   ~

               Changelog


2018-01-19

- updated code to handle the different
  way this one game handles data disks
  [thanks Marco V. for reporting this]

2018-01-15

- initial release

---------------------------------------
A 4am and san inc crack        No. 1616
------------------EOF------------------
